﻿// Bravura : A music composition and audio synthesis framework.
//
// Copyright © 2011 Paul Vanukoff (pvanukoff@gmail.com)
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using Bravura.Contracts.PluginTypes.Commands;
using Bravura.Core.Common;
using Bravura.Core.MusicLib;

namespace Bravura.Contracts.Directives
{
	/// <summary>
	/// Encapsulates a note-on and a note-off for an instrument.
	/// </summary>
	public class Note : IDirective
	{
		private bool _inited = false;
		private SendVoiceNoteOn _noteOn = null;
		private SendVoiceNoteOff _noteOff = null;
		private SetVoiceControl _voiceCtl = null;
		private SetVoiceSetting _voiceStg = null;

		#region Properties

		/// <summary>
		/// The tick when the note-on will be triggered.
		/// </summary>
		public int NoteOnTime
		{
			get;
			set;
		}

		/// <summary>
		/// The tick when the note-off will be triggered.
		/// </summary>
		public int NoteOffTime
		{
			get;
			set;
		}

		/// <summary>
		/// The voice number that the note-on and note-off will be sent.
		/// </summary>
		public int Voice
		{
			get;
			set;
		}

		/// <summary>
		/// The note-on sound. Optional.
		/// </summary>
		public double? Sound
		{
			get;
			set;
		}

		/// <summary>
		/// The note-on pitch. Optional.
		/// </summary>
		public double? Pitch
		{
			get;
			set;
		}

		/// <summary>
		/// The note-on velocity. Optional.
		/// </summary>
		public double? Velocity
		{
			get;
			set;
		}

		/// <summary>
		/// Settings to apply on note-on.
		/// </summary>
		public NoteSettingList NoteOnSettings
		{
			get;
			set;
		}

		/// <summary>
		/// Controls to apply on note-on.
		/// </summary>
		public NoteControlList NoteOnControls
		{
			get;
			set;
		}

		#endregion

		#region IDirective

		public Note()
		{
			this.NoteOnSettings = new NoteSettingList();
			this.NoteOnControls = new NoteControlList();
		}

		void IDirective.Execute(ICommandTarget target, int tick)
		{
			if (!_inited)
			{
				_noteOn = new SendVoiceNoteOn { Voice = this.Voice, Pitch = this.Pitch, Sound = this.Sound, Velocity = this.Velocity };
				_noteOff = new SendVoiceNoteOff { Voice = this.Voice };
				_voiceStg = new SetVoiceSetting { Voice = this.Voice };
				_voiceCtl = new SetVoiceControl { Voice = this.Voice };
				_inited = true;
			}

			if (tick == NoteOnTime)
			{
				(_noteOn as ICommand).Execute(target);
				foreach (NoteSetting setting in this.NoteOnSettings)
				{
					_voiceStg.Key = setting.Name;
					_voiceStg.Value = setting.Value;
					(_voiceStg as ICommand).Execute(target);
				}
				foreach (NoteControl control in this.NoteOnControls)
				{
					_voiceCtl.Key = control.Name;
					_voiceCtl.Value = control.Value;
					(_voiceCtl as ICommand).Execute(target);
				}
			}

			if (tick == NoteOffTime)
			{
				(_noteOff as ICommand).Execute(target);
			}
		}

		#endregion
	}

	#region Helpers

	internal static class ListChars
	{
		public static char SplitChar = ';';
		public static char EqualChar = '=';
		public static char[] SplitChars = new char[] { SplitChar };
		public static char[] EqualChars = new char[] { EqualChar };
	}

	public struct NoteControl
	{
		public string Name
		{
			get;
			set;
		}

		public double Value
		{
			get;
			set;
		}
	}

	public struct NoteSetting
	{
		public string Name
		{
			get;
			set;
		}

		public string Value
		{
			get;
			set;
		}
	}

	[TypeConverter(typeof(NoteControlListConverter))]
	public class NoteControlList : List<NoteControl>
	{
		public NoteControlList()
		{
		}

		public NoteControlList(string input)
		{
			string[] pairs = input.Split(ListChars.SplitChars, StringSplitOptions.RemoveEmptyEntries);
			foreach (string pair in pairs)
			{
				string[] parts = pair.Split(ListChars.EqualChars);
				this.Add(new NoteControl { Name = parts[0], Value = double.Parse(parts[1]) });
			}
		}

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();
			foreach (var item in this)
			{
				sb.AppendFormat("{0}{1}{2}{3}", item.Name, ListChars.EqualChar, item.Value, ListChars.SplitChar);
			}
			return sb.ToString();
		}
	}

	[TypeConverter(typeof(NoteSettingListConverter))]
	public class NoteSettingList : List<NoteSetting>
	{
		public NoteSettingList()
		{
		}

		public NoteSettingList(string input)
		{
			string[] pairs = input.Split(ListChars.SplitChars, StringSplitOptions.RemoveEmptyEntries);
			foreach (string pair in pairs)
			{
				string[] parts = pair.Split(ListChars.EqualChars);
				this.Add(new NoteSetting { Name = parts[0], Value = parts[1] });
			}
		}

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder();
			foreach (var item in this)
			{
				sb.AppendFormat("{0}{1}{2}{3}", item.Name, ListChars.EqualChar, item.Value, ListChars.SplitChar);
			}
			return sb.ToString();
		}
	}

	public class NoteControlListConverter : TypeConverter
	{
		public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
		{
			return sourceType == typeof(string);
		}

		public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
		{
			object output = null;

			if (value.GetType() == typeof(string))
			{
				output = new NoteControlList((string)value);
			}

			return output;
		}
	}

	public class NoteSettingListConverter : TypeConverter
	{
		public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
		{
			return sourceType == typeof(string);
		}

		public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
		{
			object output = null;

			if (value.GetType() == typeof(string))
			{
				output = new NoteSettingList((string)value);
			}

			return output;
		}
	}

	#endregion
}
